from .log import log_gui

def sgn(n):
  """Returns:
  n < 0: -1
  n == 0: 0
  n > 0: 1"""
  return cmp(n, 0)

def SafeCastToInt(n):
  try:
    return int(n)
  except:
    return 0

class InertialMover:
  def __init__(self, friction, acceleration=0):
    self._friction = friction
    self._acceleration = acceleration
    # The y coordinate where the mouse was last pressed on a drag.
    self._prev_position = None
    # The last change in y during a drag
    self._prev_delta = None
    # The time (in time_t) of the last position update
    self._prev_update_position_time = None
    # The time (in time_t) of the last speed update
    self._prev_update_speed_time = None
    # The last interval over which a move of delta occurred (sec)
    self._prev_update_interval = None
    # The last rate of movement in units per second.
    self._speed = None

  def TeleportWithoutMomentum(self, position, update_time):
    """Remove all momentum and teleport to a new position."""
    log_gui("InertialMover: Reset to", position)
    self.Reset()
    self.AbsoluteMove(position, update_time)

  def CanInferStillness(self, now):
    """If no movement has occurred for 100ms then return True."""
    return (self._prev_update_position_time is not None and
            self._prev_position is not None and
            now - self._prev_update_position_time > 0.1)

  def Reset(self):
    """Lose all momentum."""
    self._prev_position = None
    self._speed = None
    self._prev_update_position_time = None
    self._prev_update_speed_time = None
    self._prev_delta = None
    self._prev_update_interval = None

  def IsInitialized(self):
    """Return true if we have enough information to compute momentum."""
    return self._prev_position is not None

  def RelativeMove(self, distance, update_time):
    #log_gui("RelativeMove by", distance)
    return self.AbsoluteMove(self._prev_position + distance, update_time)

  def TimeSinceLastSpeedUpdate(self, now):
    if self._prev_update_speed_time is not None:
      return now - self._prev_update_speed_time
    elif self._prev_update_position_time is not None:
      return now - self._prev_update_position_time
    else:
      return None

  def TimeSinceLastPositionUpdate(self, now):
    return now - self._prev_update_position_time

  def Update(self, now):
    """Update the position at the current speed given a new time."""
    seconds = self.TimeSinceLastSpeedUpdate(now)
    speed = self.UpdateSpeed(now)
    if speed:
      distance = speed * seconds
      dy = self.RelativeMove(distance, now)
    else:
      dy = 0
    return dy

  def AbsoluteMove(self, position, update_time):
    """Add new position information, updating the internal state. Friction is
    not applied (the object is locked to the engine that moves it)."""
    #log_gui("AbsoluteMove to position", position)
    if self._prev_position is not None:
      self._prev_delta = position - self._prev_position
    self._prev_position = position
    if self._prev_update_position_time is not None:
      self._prev_update_interval = update_time - self._prev_update_position_time
    self._prev_update_position_time = update_time
    return self._prev_delta

  def UpdateSpeed(self, update_time):
    """Initialize the speed or apply friction to it. Return the speed."""
    if self._speed is None:
      if self._prev_position is not None and self._prev_update_interval:
        self._speed = self._prev_delta / self._prev_update_interval
        log_gui("UpdateSpeed: Initialize speed to delta/interval", self._speed)
      else:
        self._speed = 0
    else:
      if self._prev_update_speed_time:
        self._ApplyPhysicsModel(update_time - self._prev_update_speed_time)
    self._prev_update_speed_time = update_time
    if self._speed is None:
      return 0
    else:
      return self._speed

  def _ApplyPhysicsModel(self, seconds):
    """Apply friction and acceleration."""
    log_gui("- Apply physics. Speed before=", self._speed)
    self._speed = sgn(self._speed) * max(0,
        abs(self._speed) - self._friction * seconds)
    self._speed += self._acceleration * seconds
    log_gui("                 Speed after =", self._speed)
    if self._speed == 0:
      self._speed = None

  def IsStopped(self):
    return not self._speed

  def DebugString(self, now):
    return "Friction=%s, position=%s, delta=%s, last position update=%s, " \
           "last speed speed=%s, interval=%s, speed=%s" % (
               self._friction,
               self._prev_position,
               self._prev_delta,
               SafeCastToInt(self._prev_update_position_time) - now,
               SafeCastToInt(self._prev_update_speed_time) - now,
               self._prev_update_interval,
               self._speed)


